import sys
import getopt
import time
import Checksum
import BasicSender
import os
import math
import random

'''
This is a skeleton sender class. Create a fantastic transport protocol here.
'''
class Sender(BasicSender.BasicSender):
    window_size = 5
    data_size = 500
    timeout = 0.5

    packets_to_send = None
    packets_to_send_times = None 
    total_number_of_packets = 0
    num_packets_in_first_burst = None
    
    sliding_window_beginning = 0

    seqno = None 

    message_type = None
    msg = None
    next_msg = None
    debug = True

    #Prepare the first burst of packets
    def prepare_packets(self):
        if self.message_type != 'end':
            for i in xrange(self.window_size):
                if self.message_type == 'end':
                    return
                else:
                    self.next_msg = self.infile.read(self.data_size)
                
                if debug:
                    print 'preparing packet with seqno: %s' % i
                    print 'msg: %s' % self.msg
                    print 'next_msg: %s' % self.next_msg

                self.message_type = 'data'
                if self.seqno == 0:
                    self.message_type = 'start'
                elif self.next_msg == '':
                    self.message_type = 'end'

                packet = self.make_packet(self.message_type, self.seqno, self.msg)

                self.packets_to_send.append(packet)

                self.seqno += 1
                self.msg = self.next_msg



    #Sends the first burst of packets (Working)
    def send_first_burst(self):
        for packet in self.packets_to_send:
            self.send(packet)
            self.packets_to_send_times.append((packet, time.time() + self.timeout))
            self.num_packets_in_first_burst += 1
            if debug:
                print 'sent: %s' % packet

    def get_seqno(self, temp):
        pieces = temp.split('|')
        return int(pieces[1])

    #Handles the response
    def handle_response(self, response_packet, soonest_packet):
        if debug:
            if Checksum.validate_checksum(response_packet):
                print "recv: %s" % response_packet
            else:
                print "recv: %s <--- CHECKSUM FAILED" % response_packet

        #Ack timeouts
        if response_packet == None:
            self.send(soonest_packet)
            self.packets_to_send_times.append((soonest_packet, time.time() + self.timeout))
            self.total_number_of_packets += 1
            if debug:
                print 'sent soonest_packet: %s' % soonest_packet
            return

        #Ack is corrupted
        elif not(Checksum.validate_checksum(response_packet)):
            self.send(soonest_packet)
            self.packets_to_send_times.append((soonest_packet, time.time() + self.timeout))
            self.total_number_of_packets += 1
            if debug:
                print 'sent soonest_packet: %s' % soonest_packet
            return

        #Duplicate or ack n when ack n+1 was already received
        elif self.get_seqno(response_packet) < self.sliding_window_beginning:
            return

        #Packet lost on transmission
        elif self.get_seqno(response_packet) == self.sliding_window_beginning:
            self.send(soonest_packet)
            self.packets_to_send_times.append((soonest_packet, time.time() + self.timeout))
            self.total_number_of_packets += 1
            if debug:
                print 'sent soonest_packet: %s' % soonest_packet
            return

        #Ack is ok
        else:
            ack_num = self.get_seqno(response_packet)
            for packet,timeout in self.packets_to_send_times:
                if ack_num > self.get_seqno(packet):
                    self.packets_to_send_times.remove((packet, timeout))
                    
            previous_sliding_window_beginning = self.sliding_window_beginning
            self.sliding_window_beginning = self.get_seqno(response_packet)
            diff = self.sliding_window_beginning - previous_sliding_window_beginning
            sliding_window_end = self.sliding_window_beginning + self.window_size
            for i in range(sliding_window_end - diff, sliding_window_end):
                if self.message_type == 'end':
                    # this means that the end was sent already
                    return
                else:
                    self.next_msg = self.infile.read(self.data_size)
                    
                self.message_type = 'data'
                if self.next_msg == '':
                    self.message_type = 'end'
                    
                temp = self.make_packet(self.message_type, self.seqno, self.msg)
                
                self.send(temp)
                self.packets_to_send_times.append((temp, time.time() + self.timeout))
                
                if debug:
                    print 'sending packet: %s' % temp
                
                self.seqno = self.seqno + 1
                self.msg = self.next_msg
            return

    """
    #Main sending and receiving loop (window_size to total_number_of_packets - window_size)
    def receive_send_loop(self):
        if debug:
            print self.packets_to_send_times
                
        packet, timeout = self.packets_to_send_times.pop(0)
        response = self.receive(timeout - time.clock())
        if debug:
            print 'RESPONSE: %s' % response
        i = self.num_packets_in_first_burst
        if debug: 
            print "total number of packets = %f" %self.total_number_of_packets
        while i < self.total_number_of_packets:
            self.handle_response(response, packet)
            if debug:
                print self.packets_to_send_times
            #self.handle_response_fail_check(response, packet)
            packet, timeout = self.packets_to_send_times.pop(0)
            wait_time = timeout - time.clock()
            if wait_time < 0:
                response = None
            else:
                response = self.receive(timeout - time.clock())
                
            if debug:
                print 'RESPONSE: %s' % response
                print 'time to wait: %s' % (timeout -time.time())
            i += 1
        if debug: 
            print "total number of packets = %f" %self.total_number_of_packets
        self.infile.close()
    """
        
    # receive send loop
    def receive_send_loop(self):
        
        while len(self.packets_to_send_times) > 0:
            if debug:
                for packet, timeout in self.packets_to_send_times:
                    print 'packet: %s' % packet
                    print 'timeout: %s' % timeout
                    
            packet, timeout = self.packets_to_send_times.pop(0)
            wait_time = timeout - time.time()
            
            if debug:
                print 'wait_time: %s' % wait_time
                
            if wait_time < 0:
                response = None
            else:
                response = self.receive(timeout - time.time())
                
            if debug:
                print 'Response: %s' % response
                
            self.handle_response(response, packet)


    #Starts the sender
    def start(self):
        if debug:
            print 'sender is beginning'
        self.packets_to_send = []
        self.packets_to_send_times = []
        # figure out something for this var in case it's stdin
        self.total_number_of_packets = math.ceil(os.path.getsize(self.infile.name) / float(self.data_size))
        self.num_packets_in_first_burst = 0

        self.seqno = 0
        self.msg = self.infile.read(self.data_size)
    
        self.prepare_packets()
        self.send_first_burst()
        #self.receive_send_loop_test_packet_loss()
        #self.receive_send_loop_fail_end()
        #self.receive_send_loop()
        self.receive_send_loop()
        self.infile.close()
        
    
    # receive send loop for taking in stdin
    def receive_send_loop_stdin_test_packet_loss(self):
        
        i = self.num_packets_in_first_burst
        
        while len(self.packets_to_send_times) > 0:
            if debug:
                for packet, timeout in self.packets_to_send_times:
                    print 'packet: %s' % packet
                    print 'timeout: %s' % timeout
                    
            packet, timeout = self.packets_to_send_times.pop(0)
            wait_time = timeout - time.time()
            
            if debug:
                print 'wait_time: %s' % wait_time
                
            if wait_time < 0:
                response = None
            else:
                response = self.receive(timeout - time.time())
                
            # imlement packet dropping
            if (i % 3) != 0:
                response = None
                
            if debug:
                print 'Response: %s' % response
                
            self.handle_response(response, packet)
            
            i += 1
        print 'Number of iterations: %s' % i

    #New receive send loop moded to test packet loss
    def receive_send_loop_test_packet_loss(self):
        packet, timeout = self.packets_to_send_times.pop(0)
        response = self.receive(timeout - time.clock())
        i = self.num_packets_in_first_burst
        if debug: 
            print "total number of packets = %f" %self.total_number_of_packets
        while i < self.total_number_of_packets:
            self.handle_response(response, packet)
            packet, timeout = self.packets_to_send_times.pop(0)
            if  (i % 3) == 0:
                response = self.receive(math.fabs(timeout - time.clock()) + timeout/5)
            else:
                response = None
            i += 1
        if debug: 
            print "total number of packets = %f" %self.total_number_of_packets
        self.infile.close()
        
        #Main sending and receiving loop (window_size to total_number_of_packets - window_size)
    def receive_send_loop_fail_end(self):
        packet, timeout = self.packets_to_send_times.pop(0)
        response = self.receive(timeout - time.clock())
        i = 0
        tmp = True
        if debug: 
            print "total number of packets = %f" %self.total_number_of_packets
        while i < self.total_number_of_packets:
            self.handle_response(response, packet)
            #self.handle_response_fail_check(response, packet)
            packet, timeout = self.packets_to_send_times.pop(0)
            wait_time = timeout - time.clock()
            if wait_time < 0:
                response = None
            else:
                response = self.receive(timeout - time.clock())
            if i + 2 == self.total_number_of_packets and tmp:
                response = None
                print "Here"
            if i + 2 == self.total_number_of_packets:
                tmp = False
                print "There"
            if debug:
                print timeout -time.time()
            i += 1
        if debug: 
            print "total number of packets = %f" %self.total_number_of_packets
        self.infile.close()    

    #Handles the response
    def handle_response_fail_check(self, response_packet, soonest_packet):
        if debug:
            if Checksum.validate_checksum(response_packet):
                print "recv: %s" % response_packet
            else:
                print "recv: %s <--- CHECKSUM FAILED" % response_packet

        #Ack timeouts
        if response_packet == None:
            self.send(soonest_packet)
            self.packets_to_send_times.append((soonest_packet, time.time() + self.timeout))
            self.total_number_of_packets += 1
            if debug:
                print 'sent soonest_packet: %s' % soonest_packet
            return

        #Ack is corrupted
        elif not(random.randint(0,1)):
            self.send(soonest_packet)
            self.packets_to_send_times.append((soonest_packet, time.time() + self.timeout))
            self.total_number_of_packets += 1
            if debug:
                print 'sent soonest_packet: %s' % soonest_packet
            return

        #Duplicate or ack n when ack n+1 was already received
        elif self.get_seqno(response_packet) < self.sliding_window_beginning:
            return

        #Packet lost on transmission
        elif self.get_seqno(response_packet) == self.sliding_window_beginning:
            self.send(soonest_packet)
            self.packets_to_send_times.append((soonest_packet, time.time() + self.timeout))
            self.total_number_of_packets += 1
            if debug:
                print 'sent soonest_packet: %s' % soonest_packet
            return

        #Ack is ok
        else:
            ack_num = self.get_seqno(response_packet)
            for packet,timeout in self.packets_to_send_times:
                if ack_num > self.get_seqno(packet):
                    self.packets_to_send_times.remove((packet, timeout))
                    
            previous_sliding_window_beginning = self.sliding_window_beginning
            self.sliding_window_beginning = self.get_seqno(response_packet)
            diff = self.sliding_window_beginning - previous_sliding_window_beginning
            sliding_window_end = self.sliding_window_beginning + self.window_size
            for i in range(sliding_window_end - diff, sliding_window_end):
                if self.message_type == 'end':
                    # this means that the end was sent already
                    return
                else:
                    self.next_msg = self.infile.read(self.data_size)
                    
                self.message_type = 'data'
                if self.next_msg == '':
                    self.message_type = 'end'
                    
                temp = self.make_packet(self.message_type, self.seqno, self.msg)
                
                self.send(temp)
                self.packets_to_send_times.append((temp, time.time() + self.timeout))
                
                if debug:
                    print 'sending packet: %s' % temp
                    
                self.seqno = self.seqno + 1
                self.msg = self.next_msg
            return
        
'''
This will be run if you run this script from the command line. You should not
need to change any of this.
'''
if __name__ == "__main__":
    def usage():
        print "BEARS-TP Sender"
        print "-f FILE | --file=FILE The file to transfer; if empty reads from STDIN"
        print "-p PORT | --port=PORT The destination port, defaults to 33122"
        print "-a ADDRESS | --address=ADDRESS The receiver address or hostname, defaults to localhost"
        print "-d | --debug Print debug messages"
        print "-h | --help Print this usage message"

    try:
        opts, args = getopt.getopt(sys.argv[1:],
        "f:p:a:d", ["file=", "port=", "address=", "debug="])
    except:
        usage()
        exit()

    port = 33122
    dest = "localhost"
    filename = None
    debug = False

    for o, a in opts:
        if o in ("-f", "--file="):
            filename = a
        elif o in ("-p", "--port="):
            port = int(a)
        elif o in ("-a", "--address="):
            dest = a
        elif o in ("-d", "--debug="):
            debug = True

    s = Sender(dest, port, filename, debug)
    try:
        if filename:
            s.start()
        else:
            s.stdin_start()
    except (KeyboardInterrupt, SystemExit):
        exit()

